use ansi_term::Colour;
use common::{self, Error::*, SearchDoc};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::time::Duration;
use tantivy::collector::TopDocs;
use tantivy::query::QueryParser;
use tantivy::query::{FuzzyTermQuery, Query, RegexQuery, TermQuery};
use tantivy::schema::*;
use tantivy::{doc, IndexReader, Snippet, SnippetGenerator};
/**!
search module

*/
use tokio;


#[cfg(feature = "tracelog")]
use tracing::{debug, error, event, info, instrument, Level};
#[cfg(feature = "rotatelog")]
use log::{debug, error, info, trace, warn};

use walkdir::WalkDir;

mod nss;

pub use nss::Nss;
use std::sync::Arc;

const RUST_EXT: &str = "rs";
const GO_EXT: &str = "go";

pub async fn shandong_main(nss: Arc<Nss>, file_path: String) -> Result<(), common::Error> {
    /*
     walk_path(path, nss).await;
    */
    walk_path(file_path.as_str(), &nss).await?;
    Ok(())
}

// one possible implementation of walking a directory only visiting files
async fn walk_path(dir: &str, nss: &Nss) -> Result<(), common::Error> {
    let mut num = 0;
    for entry in WalkDir::new(dir)
        .into_iter()
        .filter_map(Result::ok)
        .filter(|e| {
            e.file_name()
                .to_str()
                .map(|s| !s.starts_with("."))
                .unwrap_or(false)
        })
        .filter(|e| !e.file_type().is_dir())
    {
        let fext = entry
            .path()
            .extension()
            .unwrap_or(entry.file_name())
            .to_string_lossy();
        match fext.into_owned().as_str() {
            RUST_EXT => {
                if !entry.file_name().to_string_lossy().eq(RUST_EXT) {
                    debug!(
                        "begin to build index for {}",
                        entry.file_name().to_string_lossy()
                    );
                    build_index(&entry.path().to_string_lossy().to_string(), nss).await?;
                }
            }
            GO_EXT => {
                if !entry.file_name().to_string_lossy().eq(GO_EXT) {
                    println!(
                        "begin to build index for {}",
                        entry.file_name().to_string_lossy()
                    );
                    build_index(&entry.path().to_string_lossy().to_string(), nss).await?;
                }
            }
            _ => continue,
        }
        num += 1;
        //thread::sleep(Duration::from_secs(1));
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
    println!("Total = {num}");
    Ok(())
}

async fn build_index(filename: &str, nss: &Nss) -> Result<(), common::Error> {
    // 在生成输出之前，文件主体必须存在于当前路径中
    let mut content = String::new();
    let path = Path::new(filename);
    // 以只读方式打开路径，返回 `io::Result<File>`
    File::open(&path)?.read_to_string(&mut content)?;

    let reader = nss.get_reader();
    let (docid, author, fname, body) = nss.get_tables();
    let docid_exist = Term::from_field_text(docid, filename);
    // Oops our frankenstein doc seems misspelled
    if let Some(_a) = extract_doc_by_docid(&reader, &docid_exist)? {
        error!(?filename, " Have been exist!");
        return Err(common::Error::AlreadyExists("exist".to_string()));
    }

    let mut index_writer = nss.get_writer().lock().await;
    // let mut index_writer = writer_lock.lock().map_err(|e| common::Error::Err4Result(e.to_string()) )?;
    // Multivalued field just need to be repeated.
    index_writer.add_document(doc!(
    docid => filename,
    author=> "lengss",
    fname => filename,
    body => content
    ))?;
    index_writer.commit()?;
    Ok(())
}

fn extract_doc_by_docid(
    reader: &IndexReader,
    docid_term: &Term,
) -> tantivy::Result<Option<Document>> {
    let searcher = reader.searcher();
    // This is the simplest query you can think of.
    // It matches all of the documents containing a specific term.
    // The second argument is here to tell we don't care about decoding positions,
    // or term frequencies.
    let term_query = TermQuery::new(docid_term.clone(), IndexRecordOption::Basic);
    let top_docs = searcher.search(&term_query, &TopDocs::with_limit(1))?;
    if let Some((_score, doc_address)) = top_docs.first() {
        let doc = searcher.doc(*doc_address)?;
        Ok(Some(doc))
    } else {
        // no doc matching this ID.
        Ok(None)
    }
}
pub async fn do_search(
    nss: Arc<Nss>,
    qry: &str,
    qtype: &str,
    page: i32,
    api: bool,
) -> Result<Vec<SearchDoc>, common::Error> {
    debug!(?qry, ?qtype, ?page);
    let index = nss.get_index();
    let reader = nss.get_reader();
    let searcher = reader.searcher();

    let (docid, author, fname, body) = nss.get_tables();
    let query: Box<dyn Query> = match qtype {
        "regexf" => Box::new(RegexQuery::from_pattern(qry, fname)?),
        "regex" => Box::new(RegexQuery::from_pattern(qry, body)?),
        "termf" => {
            error!("try to use Term Query on filename");
            let query = Term::from_field_text(fname, qry);
            Box::new(TermQuery::new(query.clone(), IndexRecordOption::Basic))
        }
        "term" => {
            let query = Term::from_field_text(body, qry);
            Box::new(TermQuery::new(query.clone(), IndexRecordOption::Basic))
        }
        "fuzzyf" => {
            let term = Term::from_field_text(fname, qry);
            Box::new(FuzzyTermQuery::new(term, 1, true))
        }
        "fuzzy" => {
            let term = Term::from_field_text(body, qry);
            Box::new(FuzzyTermQuery::new(term, 1, true))
        }
        _ => {
            debug!(?qtype, " default use QueryParser");
            let query_parser = QueryParser::for_index(&index, vec![docid, author, fname, body]);
            Box::new(query_parser.parse_query(qry)?)
        }
    };

    let top_docs = searcher.search(&query, &TopDocs::with_limit(30))?;
    let snippet_generator = SnippetGenerator::create(&searcher, &*query, body)?;
    //snippet_generator.set_max_num_chars(100);
    let mut rets: Vec<SearchDoc> = vec![];
    for (score, doc_address) in top_docs {
        let doc = searcher.doc(doc_address)?;
        let snippet = snippet_generator.snippet_from_doc(&doc);

        let mut rdoc = SearchDoc::default();
        rdoc.docid = doc
            .get_first(docid)
            .ok_or(GetError())?
            .as_text()
            .ok_or(GetError())?
            .to_string();
        rdoc.author = doc
            .get_first(author)
            .ok_or(GetError())?
            .as_text()
            .ok_or(GetError())?
            .to_string();
        rdoc.fname = doc
            .get_first(fname)
            .ok_or(GetError())?
            .as_text()
            .ok_or(GetError())?
            .to_string();

        if snippet.fragment().as_bytes().len() > 0 {
            if !api {
                rdoc.content = highlight(&snippet)?;
            } else {
                rdoc.content = highlight_console(&snippet)?;
            }
        } else {
            rdoc.content = highlight_snippert_bugfix(
                doc.get_first(body)
                    .ok_or(GetError())?
                    .as_text()
                    .ok_or(GetError())?,
                qry,
                api,
            )?;
        }

        rdoc.score = score;
        rets.push(rdoc);
    }
    Ok(rets)
}

fn highlight(snippet: &Snippet) -> Result<String, common::Error> {
    let mut result = String::new();
    let mut start_from = 0;
    let max_len = snippet.fragment().as_bytes().len();
    debug!("fragment length = {} ", max_len);
    let mut num = 0;
    for fragment_range in snippet.highlighted() {
        if start_from <= fragment_range.start && fragment_range.start < max_len {
            result.push_str(&snippet.fragment()[start_from..fragment_range.start]);
            num = 0;
        }
        if num > 0 {
            result.push_str(" | ");
        }

        result.push_str("<mark>");
        result.push_str(&snippet.fragment()[fragment_range.clone()]);
        result.push_str("</mark>");
        num += 1;
        start_from = fragment_range.end;
    }
    if start_from < max_len {
        result.push_str(&snippet.fragment()[start_from..]);
    }
    debug!("{}", result);
    Ok(result)
}

fn highlight_console(snippet: &Snippet) -> Result<String, common::Error> {
    let mut result = String::new();
    let mut start_from = 0;
    let max_len = snippet.fragment().as_bytes().len();
    let mut num = 0;
    debug!("fragment length = {}", max_len);
    for fragment_range in snippet.highlighted() {
        if start_from <= fragment_range.start && fragment_range.start < max_len {
            num = 0;
            result.push_str(&snippet.fragment()[start_from..fragment_range.start]);
        }
        if num > 0 {
            result.push_str(" | ");
        }
        let s = format!(
            "{}",
            Colour::Red.paint(&snippet.fragment()[fragment_range.clone()])
        );
        result.push_str(s.as_str());
        num += 1;
        start_from = fragment_range.end;
    }
    if start_from < max_len {
        result.push_str(&snippet.fragment()[start_from..]);
    }
    debug!("{}", result);
    Ok(result)
}

fn highlight_snippert_bugfix(body: &str, qry: &str, ansi: bool) -> Result<String, common::Error> {
    let mut result = String::new();
    const TERM_LEN: usize = 40;
    result.push_str("[ Snippert bugfix ] ");
    if let Some(fdx) = body.find(qry) {
        let left = String::from(&body[0..fdx]);
        let right = String::from(&body[fdx + qry.len()..]);
        let mut left_pos: usize = 0;
        let ll = left.chars().count();
        if ll > TERM_LEN {
            for i in 0..TERM_LEN {
                left_pos += left
                    .chars()
                    .nth(ll - 1 - i)
                    .ok_or(GetError())?
                    .to_string()
                    .len();
            }
            left_pos = left.len() - left_pos;
        }

        let mut right_pos: usize = 0;
        if right.chars().count() > TERM_LEN {
            for i in 0..TERM_LEN {
                right_pos += String::from(right.chars().nth(i).ok_or(GetError())?).len();
            }
        } else {
            right_pos = right.len();
        }

        debug!(?left_pos, ?fdx, ?right_pos);
        result.push_str(&body[left_pos..fdx]);
        if ansi {
            let term = format!("{}", Colour::Red.paint(qry));
            result.push_str(&term);
        } else {
            let term = format!("<mark>{}</mark>", qry);
            result.push_str(&term);
        }
        result.push_str(&right[..right_pos])
    }
    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_visit_dir() {
        //walk_path("/home/lengss/rust/src/road")  ;
        println!("abc");
    }
}
